package net.wy.commons.interceptor;

import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.wy.commons.util.SimpleException;
import org.apache.log4j.Logger;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
import org.springframework.web.util.WebUtils;

/**
 * 简单的错误拦截
 * @author LYM
 */
public class SimpleExceptionInterceptor extends AbstractHandlerExceptionResolver {
    public static final String DEFAULT_EXCEPTION_ATTRIBUTE = "exception";
    private Logger log;
    private Properties exceptionMappings;
    private String defaultErrorView;
    private Integer defaultStatusCode;
    private Map<String, Integer> statusCodes;
    private String exceptionAttribute;

    public SimpleExceptionInterceptor() {
        this.log = Logger.getLogger(getClass());
        this.statusCodes = new HashMap<String, Integer>();
        this.exceptionAttribute = "exception";
    }

    public void setExceptionMappings(Properties mappings) {
        this.exceptionMappings = mappings;
    }

    public void setDefaultErrorView(String defaultErrorView) {
        this.defaultErrorView = defaultErrorView;
    }

    public void setStatusCodes(Properties statusCodes) {
        for (Enumeration<?> enumeration = statusCodes.propertyNames(); enumeration.hasMoreElements(); ) {
            String viewName = (String) enumeration.nextElement();
            Integer statusCode = Integer.valueOf(statusCodes.getProperty(viewName));
            this.statusCodes.put(viewName, statusCode);
        }
    }

    public void setDefaultStatusCode(int defaultStatusCode) {
        this.defaultStatusCode = Integer.valueOf(defaultStatusCode);
    }

    public void setExceptionAttribute(String exceptionAttribute) {
        this.exceptionAttribute = exceptionAttribute;
    }

    protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        if ((ex instanceof SimpleException)) {
            StringBuffer url = request.getRequestURL();
            this.log.info(url.toString() + " " + ex.getMessage());
        } else {
            this.log.error("请求失败", ex);
        }

        String viewName = determineViewName(ex, request);
        if (viewName != null) {
            Integer statusCode = determineStatusCode(request, viewName);
            if (statusCode != null) {
                applyStatusCodeIfPossible(request, response, statusCode.intValue());
            }
            return getModelAndView(viewName, ex, request);
        }
        return null;
    }

    protected String determineViewName(Exception ex, HttpServletRequest request) {
        String viewName = null;

        if (this.exceptionMappings != null) {
            viewName = findMatchingViewName(this.exceptionMappings, ex);
        }

        if ((viewName == null) && (this.defaultErrorView != null)) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Resolving to default view '" + this.defaultErrorView + "' for exception of type [" + ex.getClass().getName() + "]");
            }

            viewName = this.defaultErrorView;
        }
        return viewName;
    }

    protected String findMatchingViewName(Properties exceptionMappings, Exception ex) {
        String viewName = null;
        String dominantMapping = null;
        int deepest = 2147483647;
        for (Enumeration<?> names = exceptionMappings.propertyNames(); names.hasMoreElements(); ) {
            String exceptionMapping = (String) names.nextElement();
            int depth = getDepth(exceptionMapping, ex);
            if ((depth >= 0) && (depth < deepest)) {
                deepest = depth;
                dominantMapping = exceptionMapping;
                viewName = exceptionMappings.getProperty(exceptionMapping);
            }
        }
        if ((viewName != null) && (this.logger.isDebugEnabled())) {
            this.logger.debug("Resolving to view '" + viewName + "' for exception of type [" + ex.getClass().getName() + "], based on exception mapping [" + dominantMapping + "]");
        }

        return viewName;
    }

    protected int getDepth(String exceptionMapping, Exception ex) {
        return getDepth(exceptionMapping, ex.getClass(), 0);
    }

    private int getDepth(String exceptionMapping, Class<?> exceptionClass, int depth) {
        if (exceptionClass.getName().contains(exceptionMapping)) {
            return depth;
        }

        if (exceptionClass.equals(Throwable.class)) {
            return -1;
        }
        return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
    }

    protected Integer determineStatusCode(HttpServletRequest request, String viewName) {
        if (this.statusCodes.containsKey(viewName)) {
            return (Integer) this.statusCodes.get(viewName);
        }
        return this.defaultStatusCode;
    }

    protected void applyStatusCodeIfPossible(HttpServletRequest request, HttpServletResponse response, int statusCode) {
        if (!WebUtils.isIncludeRequest(request)) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Applying HTTP status code " + statusCode);
            }
            response.setStatus(statusCode);
            request.setAttribute("javax.servlet.error.status_code", Integer.valueOf(statusCode));
        }
    }

    protected ModelAndView getModelAndView(String viewName, Exception ex, HttpServletRequest request) {
        String errorPage = (String) request.getAttribute("errorPage");
        ModelAndView mv = new ModelAndView(errorPage == null ? viewName : errorPage);
        if (this.exceptionAttribute != null) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Exposing Exception as model attribute '" + this.exceptionAttribute + "'");
            }
            ErrorObject error = null;
            if ((ex instanceof SimpleException)) {
                SimpleException busException = (SimpleException) ex;
                String errorText = "";
                error = new ErrorObject(busException.getCode(), errorText, busException.getViewData());
            } else {
                error = new ErrorObject(this.exceptionAttribute, ex.getMessage(), null);
            }
            mv.addObject("errorObject", error);
        }
        return mv;
    }

    public static class ErrorObject {
        private String errorCode;
        private String errorText;
        private Object errorData;

        public Object getErrorData() {
            return this.errorData;
        }

        public void setErrorData(Object errorData) {
            this.errorData = errorData;
        }

        public ErrorObject(String errorCode, String errorText, Object errorData) {
            this.errorCode = errorCode;
            this.errorText = errorText;
            this.errorData = errorData;
        }

        public String getErrorCode() {
            return this.errorCode;
        }

        public void setErrorCode(String errorCode) {
            this.errorCode = errorCode;
        }

        public String getErrorText() {
            return this.errorText;
        }

        public void setErrorText(String errorText) {
            this.errorText = errorText;
        }
    }
}
